package io.servicios;

import io.Carpeta;
import io.ItemAgenda;
import io.ListaSincronizada;
import io.bd.GestorBD;
import io.red.ConfigurableClientSocketFactory;
import io.red.ConfigurableServerSocketFactory;
import java.io.File;
import java.net.InetAddress;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author Mariano Zapata
 */
public class GestorServicios extends Thread {
    
    private ListaSincronizada<Conexion> conexiones;
    private boolean vida;
    private GestorServiciosListener gsListener;
    private GestorBD gbd;
    private Registry servidorRMI;
    private int idConexiones;
    private int idArchivosTemporales;
    private GestorWidgetAgenda gwa;
    
    private static final String DIRECTORIO_TEMP= System.getProperty("user.dir") + "/temp/";
    private static final String NOMBRE_SERVICIOS= "ServiciosServidor";
    private static final String SERVICIOS_CLIENTE= "ServiciosCliente";
    private static final String ERROR_ID_CONEXIONES= "Se han agotado los identificadores de conexiones.";
    private static final String ERROR_ID_ARCHIVOS_TEMPORALES= "Se han agotado los identificadores de conexiones.";
    private static final long TIEMPO_CONTROL= 5000;
    
    public GestorServicios()
    {
        super("Gestor de Servicios");
        idConexiones= Integer.MAX_VALUE;
        idArchivosTemporales= Integer.MAX_VALUE;
        conexiones= new ListaSincronizada<Conexion>();
        gsListener= null;
        gbd= new GestorBD();
        servidorRMI= null;
        gwa= null;
    }
    
    public void iniciar(InetAddress ip, int puertoRMI) throws ClassNotFoundException, SQLException,
            RemoteException, InterruptedException
    {
        this.limpiarDirectorioTemporal();
        gbd.conectar();
        if (servidorRMI == null)
        {
            ConfigurableClientSocketFactory ccsf= new ConfigurableClientSocketFactory();
            ConfigurableServerSocketFactory cssf= new ConfigurableServerSocketFactory(ip);
            servidorRMI= LocateRegistry.createRegistry(puertoRMI, ccsf, cssf);
            ServiciosServidor serviciosServidor= new ServiciosServidor(this, gbd);
            servidorRMI.rebind(NOMBRE_SERVICIOS, serviciosServidor);
            gwa= new GestorWidgetAgenda(this, gbd);
            vida= true;
            this.start();
            gwa.start();
        }
    }
    
    @Override
    @SuppressWarnings("SleepWhileHoldingLock")
    public void run()
    {
        List<Conexion> conexionesCaidas= new ArrayList<Conexion>();
        while (vida)
        {
            try {
                Thread.sleep(TIEMPO_CONTROL);
                conexionesCaidas.clear();
                conexiones.adquirir();
                for (Conexion con : conexiones)
                {
                    try {
                        con.getInterfazServiciosCliente().estaVivo();
                    } catch (RemoteException ex) {gbd.cerrarTodos(con.getIdConexion());
                                                  conexionesCaidas.add(con);}
                }
                if (!conexionesCaidas.isEmpty())
                {
                    conexiones.removeAll(conexionesCaidas);
                    this.notificarEventoConexion(conexiones.size());
                }
                conexiones.liberar();
            } catch (InterruptedException ex) {if (vida)
                                                    this.error(ex.getMessage());}
        }
    }
    
    //EL SERVIDOR RMI Y EL HILO NUNCA SE DETIENEN.
    public boolean detener(boolean hayFalla) throws InterruptedException, SQLException
    {
        conexiones.adquirir();
        if (!hayFalla && !conexiones.isEmpty())
        {
            conexiones.liberar();
            return false;
        }
        conexiones.clear();
        this.notificarEventoConexion(conexiones.size());
        conexiones.liberar();
        gbd.desconectar(); //NUNCA ES NULO.
        gwa.detener(); //NUNCA ES NULO.
        vida= false;
        this.interrupt();
        return true;
    }
    
    public int nuevaConexion(String ipCliente, int puertoRMICliente) throws RemoteException, NotBoundException
    {
        Conexion con= new Conexion(new Integer(idConexiones));
        idConexiones-= 1;
        if (idConexiones == Integer.MIN_VALUE)
            this.error(ERROR_ID_CONEXIONES);
        try {
            Registry servidorRMIDelCliente= LocateRegistry.getRegistry(ipCliente, puertoRMICliente);
            InterfazServiciosCliente serviciosCliente=
                    (InterfazServiciosCliente) servidorRMIDelCliente.lookup(SERVICIOS_CLIENTE);
            con.setServidorRMIEnCliente(servidorRMIDelCliente);
            con.setInterfazServiciosCliente(serviciosCliente);
            conexiones.adquirir();
            conexiones.add(con);
            this.notificarEventoConexion(conexiones.size());
            con.getInterfazServiciosCliente().setProximosEventos(gwa.getProximosEventos());
            conexiones.liberar();
            return con.getIdConexion();
        } catch (InterruptedException ex) {this.error(ex.getMessage());
                                           return Integer.MIN_VALUE;}
    }
    
    public void quitarConexion(int idConexion)
    {
        int indice= -1;
        try {
            conexiones.adquirir();
            for (Conexion con : conexiones)
            {
                if (con.getIdConexion() == idConexion)
                {
                    indice= conexiones.indexOf(con);
                    gbd.cerrarTodos(con.getIdConexion());
                    break;
                }
            }
            if (indice >= 0)
            {
                conexiones.remove(indice);
                this.notificarEventoConexion(conexiones.size());
            }
            conexiones.liberar();
        } catch (InterruptedException ex) {this.error(ex.getMessage());}
    }
    
    public String getDirectorioTemporal() {
        return DIRECTORIO_TEMP;
    }
    
    public int getIdArchivoTemporal()
    {
        int ret= new Integer(idArchivosTemporales);
        idArchivosTemporales-= 1;
        if (idArchivosTemporales == Integer.MIN_VALUE)
            this.error(ERROR_ID_ARCHIVOS_TEMPORALES);
        return ret;
    }
    
    private void limpiarDirectorioTemporal()
    {
        File dirTemp= new File(DIRECTORIO_TEMP);
        File [] archivos= dirTemp.listFiles();
        for (File f : archivos)
            f.delete();
    }
    
    public void avisoActualizarCarpeta(Carpeta c, int idNoAvisar, int operacion)
    {
        try {
            conexiones.adquirir();
            for (Conexion con : conexiones)
            {
                try {
                    if (con.getIdConexion() != idNoAvisar)
                        con.getInterfazServiciosCliente().actualizarCarpeta(c, operacion);
                } catch (RemoteException ex) {} //NO SE HACE NADA PORQUE LA FUNCIÓN RUN DETECTARÁ SI HAY CONEXIONES CAÍDAS.
            }
            conexiones.liberar();
        } catch (InterruptedException ex) {this.error(ex.getMessage());}
    }
    
    public void avisoCortarObjeto(Object o, Carpeta vieja, Carpeta nueva, int idNoAvisar)
    {
        try {
            conexiones.adquirir();
            for (Conexion con : conexiones)
            {
                try {
                    if (con.getIdConexion() != idNoAvisar)
                        con.getInterfazServiciosCliente().cortarObjeto(o, vieja, nueva);
                } catch (RemoteException ex) {} //NO SE HACE NADA PORQUE LA FUNCIÓN RUN DETECTARÁ SI HAY CONEXIONES CAÍDAS.
            }
            conexiones.liberar();
        } catch (InterruptedException ex) {this.error(ex.getMessage());}
    }
    
    public void avisoQuitarDeCarpeta(Carpeta c, Object o, int idNoAvisar)
    {
        try {
            conexiones.adquirir();
            for (Conexion con : conexiones)
            {
                try {
                    if (con.getIdConexion() != idNoAvisar)
                        con.getInterfazServiciosCliente().quitarDeCarpeta(c, o);
                } catch (RemoteException ex) {} //NO SE HACE NADA PORQUE LA FUNCIÓN RUN DETECTARÁ SI HAY CONEXIONES CAÍDAS.
            }
            conexiones.liberar();
        } catch (InterruptedException ex) {this.error(ex.getMessage());}
    }
    
    public void avisoActualizarListaAgenda(List<ItemAgenda> items, int idNoAvisar)
    {
        try {
            conexiones.adquirir();
            for (Conexion con : conexiones)
            {
                try {
                    if (con.getIdConexion() != idNoAvisar)
                        con.getInterfazServiciosCliente().actualizarListaAgenda(items);
                } catch (RemoteException ex) {} //NO SE HACE NADA PORQUE LA FUNCIÓN RUN DETECTARÁ SI HAY CONEXIONES CAÍDAS.
            }
            conexiones.liberar();
        } catch (InterruptedException ex) {this.error(ex.getMessage());}
    }
    
    public void avisoQuitarDeListaAgenda(ItemAgenda item, int idNoAvisar)
    {
        try {
            conexiones.adquirir();
            for (Conexion con : conexiones)
            {
                try {
                    if (con.getIdConexion() != idNoAvisar)
                        con.getInterfazServiciosCliente().quitarDeListaAgenda(item);
                } catch (RemoteException ex) {} //NO SE HACE NADA PORQUE LA FUNCIÓN RUN DETECTARÁ SI HAY CONEXIONES CAÍDAS.
            }
            conexiones.liberar();
        } catch (InterruptedException ex) {this.error(ex.getMessage());}
    }
    
    public void avisoSetProximosEventos(List<ItemAgenda> eventos)
    {
        try {
            conexiones.adquirir();
            for (Conexion con : conexiones)
            {
                try {
                    con.getInterfazServiciosCliente().setProximosEventos(eventos);
                } catch (RemoteException ex) {} //NO SE HACE NADA PORQUE LA FUNCIÓN RUN DETECTARÁ SI HAY CONEXIONES CAÍDAS.
            }
            conexiones.liberar();
        } catch (InterruptedException ex) {this.error(ex.getMessage());}
    }
    
    public void avisoTiempoCumplido()
    {
        try {
            conexiones.adquirir();
            for (Conexion con : conexiones)
            {
                try {
                    con.getInterfazServiciosCliente().tiempoCumplido();
                } catch (RemoteException ex) {} //NO SE HACE NADA PORQUE LA FUNCIÓN RUN DETECTARÁ SI HAY CONEXIONES CAÍDAS.
            }
            conexiones.liberar();
        } catch (InterruptedException ex) {this.error(ex.getMessage());}
    }
    
    public void actualizarProximosEventos() {
        gwa.actualizarProximosEventos();
    }
    
    public Conexion getConexion(int id)
    {
        Conexion ret= null;
        try {
            conexiones.adquirir();
            for (Conexion con : conexiones)
            {
                if (con.getIdConexion() == id)
                {
                    ret= con;
                    break;
                }
            }
            conexiones.liberar();
        } catch (InterruptedException ex) {this.error(ex.getMessage());}
          finally {return ret;}
    }
    
    public void error(String msj) {
        this.notificarError(msj);
    }
    
    public void agregarGestorServiciosListener(GestorServiciosListener gsl) {
        gsListener= gsl;
    }
    
    public void quitarGestorServiciosListener() {
        gsListener= null;
    }
    
    private void notificarEventoConexion(int cantidad)
    {
        if (gsListener != null)
            gsListener.gestorServiciosEventoConexion(cantidad);
    }
    
    private void notificarError(String msj)
    {
        if (gsListener != null)
            gsListener.gestorServiciosError(msj);
    }
}
